package com.tmt.datatable.ext.controller.impl;

import com.tmt.datatable.ext.controller.LoopReferenceBreaker;
import com.tmt.helper.ReflectHelper;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.PersistenceUnitUtil;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;

public class LoopReferenceBreakerImpl implements LoopReferenceBreaker {

    /**
     * 加载实体对象的指定属性。
     * 本方法不能在Spring事务环境下调用
     *
     * @param entity 实体对象。
     * @param <T>
     */
    @Override
    public <T> void breakReference(T entity, PersistenceUnitUtil unitUtil) {
        ConcurrentLinkedQueue<String> list = new ConcurrentLinkedQueue<>();
        list.add("*");
        Map<Object, ConcurrentLinkedQueue<String>> loadedObjects = new ConcurrentHashMap<>();
        loadedObjects.put(entity, list);
        breakReference(entity, "*", loadedObjects, unitUtil);
    }

    private <T> void breakReference(T entity, String propertyPath,
                                    Map<Object, ConcurrentLinkedQueue<String>> loadedObjects, PersistenceUnitUtil unitUtil) {
        //找到entity对象上的所有实体属性字段。
        List<Field> fieldList = ReflectHelper.findAllFieldByClassAnnotation(entity.getClass(), Entity.class, -1);

        fieldList.forEach(x -> {
            //如果懒加载对象没有加载则直接设置为null
            if (!unitUtil.isLoaded(entity, x.getName())) {
                ReflectHelper.setFieldValue(entity, x.getName(), null);
                return;
            }

            //读取字段值。
            Object fieldValue = ReflectHelper.getFieldValue(entity, x.getName());
            if (fieldValue == null)
                return;

            ReflectHelper.setFieldValue(entity, x.getName(), fieldValue);

            String curPropertyPath = propertyPath + "." + x.getName();

            if (fieldValue instanceof List) {
                //只要列表中有一个对象是循环加载过的，则直接进行断链操作。
                if (((List) fieldValue).stream()
                        .filter(y -> loadedObjects.containsKey(y))
                        .anyMatch(y -> loadedObjects.get(y).stream().anyMatch(z -> curPropertyPath.startsWith(z + ".")))) {
                    //此时判断为循环引用，执行断链操作。
                    ReflectHelper.setFieldValue(entity, x.getName(), null);
                    return;
                }
            }

            ConcurrentLinkedQueue<String> tmp = new ConcurrentLinkedQueue<>();
            ConcurrentLinkedQueue<String> list = loadedObjects.putIfAbsent(fieldValue, tmp);
            if (list == null) {
                list = tmp;
            }

            if (list.stream().anyMatch(y -> curPropertyPath.startsWith(y + "."))) {
                //此时判断为循环引用，执行断链操作。
                ReflectHelper.setFieldValue(entity, x.getName(), null);
                return;
            }
            list.add(curPropertyPath);

            if (fieldValue instanceof List) {
                //继续递归执行引用断链。
                ((List) fieldValue).forEach(y -> {
                    Object id = ReflectHelper.getFieldValueByAnnotation(y, Id.class, null);
                    String tmpPath = curPropertyPath + ".[" + id + "]";

                    ConcurrentLinkedQueue<String> tmpQueue = new ConcurrentLinkedQueue<>();
                    ConcurrentLinkedQueue<String> tmpList = loadedObjects.putIfAbsent(y, tmpQueue);
                    if (tmpList == null) {
                        tmpList = tmpQueue;
                    }

                    tmpList.add(tmpPath);
                    breakReference(y, tmpPath, loadedObjects, unitUtil);
                });
            } else {
                //如果对象是首次出现或不存在循环引用，则继续递归执行。
                breakReference(fieldValue, curPropertyPath, loadedObjects, unitUtil);
            }
        });
    }

}
